//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Research.ScientificWorkflow.TridentModel;
using Microsoft.Research.ScientificWorkflow.TridentComposer;
using System.Resources;
using Microsoft.Research.ScientificWorkflow.UIDesigner.Controls;
using Microsoft.Research.ScientificWorkflow.TridentUtilities;
using System.Collections.ObjectModel;
using Microsoft.Research.eResearch.Common.Eventing;

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    /// <summary>
    /// Interaction logic for ForActivityUiElement.xaml
    /// </summary>
    public partial class ForActivityUIElement : TridentUIActivityElement, ICompositeUIElement
    {
        CompositeActivityModel currentModel = null;

        private static bool isParentIteratorVisible = true;

        public static bool IsParentIteratorVisible
        {
            get { return isParentIteratorVisible; }
            set { isParentIteratorVisible = value; }
        }

        #region private variables

        /// <summary>
        /// The parameter stack.
        /// </summary>
        StackPanel paramStack;

        /// <summary>
        ///  The stackpanel for holding the input params. 
        /// </summary>
        StackPanel inputParamStack;

        /// <summary>
        /// The stackpanel for holding the output parameters.
        /// </summary>
        StackPanel outputParamStack;

        /// <summary>
        /// The grid which holds the activities.
        /// </summary>
        Grid activityHolder;

        /// <summary>
        /// Creates a Loop around the sequential activity..
        /// </summary>
        private Polyline loopLine = new Polyline();

        /// <summary>
        /// 
        /// </summary>
        private Border inputParamBorder;

        /// <summary>
        /// The icon in the for loop.
        /// </summary>
        private ForLoopIcon icon = new ForLoopIcon();

        /// <summary>
        /// The outputparameter element
        /// </summary>
        private ActivityOutputParameterElement outputParamElement;

        /// <summary>
        /// The border color.
        /// </summary>
        private Brush borderColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC6D3D8"));

        /// <summary>
        /// The children of this composite element.
        /// </summary>
        private ObservableCollection<ITridentUIElement> childElements = new ObservableCollection<ITridentUIElement>();

        #endregion

        /// <summary>
        /// Constructor. Builds the for loop UI representation.
        /// </summary>
        /// <param name="model">Activity model representing the For loop.</param>
        /// <param name="activityDataTypes">The datatypes dictionary.</param>
        public ForActivityUIElement(CompositeActivityModel model, Dictionary<String, DataTypeModel> activityDataTypes)
            : base(model, activityDataTypes)
        {
            currentModel = model;
            if (!string.IsNullOrEmpty(model.UniqueId))
            {
                this.Name = model.UniqueId + "Element";
            }

            this.InitElement();

            // Add the input parameters.
            this.AddInputParameters();

            // Add the output current index param.
            this.AddCurrentCounterParameter();

            // This is used to select the Parameter.
            this.SizeChanged += new SizeChangedEventHandler(ForActivityUiElement_SizeChanged);
            this.Loaded += new RoutedEventHandler(ForActivityUiElement_Loaded);
        }


        #region private methods

        /// <summary>
        /// Iterates ParameterDescriptionModel and add the Input parameter to Input stack
        /// </summary>
        private void AddInputParameters()
        {
            var inputParams = this.Model.InputParameters.ToList();
            inputParams.Sort(ParameterDescriptionModel.SortInputParameters);

            for (int counter = 0; counter < inputParams.Count; counter++)
            {
                ParameterDescriptionModel param = inputParams[counter] as ParameterDescriptionModel;
                if (param != null && this.InputParameterElements.ContainsKey(param.PropertyName))
                {
                    ActivityInputParameterElement inputparameter = this.InputParameterElements[param.PropertyName] as ActivityInputParameterElement;
                    if (inputparameter != null)
                    {
                        this.inputParamStack.Children.Add(inputparameter);
                    }
                }
            }
        }

        /// <summary>
        /// Add the current counter value param into the UI.
        /// </summary>
        private void AddCurrentCounterParameter()
        {
            Grid counterLoopGrid = new Grid();
            counterLoopGrid.Margin = new Thickness(3, 5, 0, 5);

            Border counterBorder = new Border();
            counterBorder.Width = 70;
            counterBorder.Height = 40;
            counterBorder.VerticalAlignment = VerticalAlignment.Top;
            counterBorder.BorderBrush = this.borderColor;
            counterBorder.BorderThickness = new Thickness(1, 1, 2, 2);
            counterBorder.CornerRadius = new CornerRadius(7, 7, 7, 7);
            ParameterDescriptionModel currentIndexModel = this.Model.OutputParameters[0];
            if (this.OutputParameterElements.ContainsKey(currentIndexModel.PropertyName))
            {
                this.outputParamElement = this.OutputParameterElements[currentIndexModel.PropertyName] as ActivityOutputParameterElement;
                outputParamElement.Width = 80;
                outputParamElement.Margin = new Thickness(2, 0, 0, 0);
                Grid.SetZIndex(outputParamElement, 2);
                counterLoopGrid.Children.Add(outputParamElement);
                counterLoopGrid.Children.Add(counterBorder);
                this.paramStack.Children.Add(counterLoopGrid);
            }
        }

        /// <summary>
        /// Initialize the body of the For activity.
        /// </summary>
        private void InitElement()
        {
            // The main activity border. This is used while focusing the activity also.
            this.MainBorder = new Border();

            this.MainBorder.VerticalAlignment = VerticalAlignment.Top;
            this.MainBorder.BorderBrush = this.borderColor;
            this.MainBorder.BorderThickness = new Thickness(1, 1, 2, 2);
            this.MainBorder.CornerRadius = new CornerRadius(7, 7, 7, 7);

            // Make the UIElement to be focusable.
            // this is used when we select a Binding and hit delete button.
            this.MainBorder.Focusable = true;

            // The background of the container is set to make it selectable.
            // if you have not set any background to any WPF control it will not raise any mouse down events.
            // This is by design of the controls in WPF.
            this.MainBorder.Background = Brushes.Transparent;

            // The activity stack.Stacks the header and the Body.
            StackPanel activityStack = new StackPanel();

            // The header.
            WorkflowStackHeader forActivityHeader = new WorkflowStackHeader(false, this.Model);
            forActivityHeader.HeaderText = this.Model.Label;

            forActivityHeader.iterationCombobox.Visibility = Visibility.Collapsed;

            // If Provenance model, show the combo boxes for iterative activities.
            if (Model.IsProvenanceMode)
            {
                currentModel.OnIndexChanged();

                forActivityHeader.iterationCombobox.Items.Clear();

                foreach (string iterate in currentModel.IterationValues)
                {
                    forActivityHeader.iterationCombobox.Items.Add(iterate);
                }

                forActivityHeader.iterationCombobox.SelectionChanged += new SelectionChangedEventHandler(headerExtraElements_SelectionChanged);
                forActivityHeader.iterationCombobox.SelectedIndex = 0;

                if (forActivityHeader.iterationCombobox.Items.Count > 0)
                {
                    currentModel.SelectedValue =
                        forActivityHeader.iterationCombobox.Items[0].ToString();

                    forActivityHeader.iterationCombobox.Visibility = Visibility.Visible;
                }

                if (forActivityHeader.iterationCombobox.Items.Count > 1)
                {
                    currentModel.NextSelectedValue =
                        forActivityHeader.iterationCombobox.Items[1].ToString();
                }

                if (forActivityHeader.iterationCombobox.Items.Count == 0)
                {
                    ForActivityUIElement.IsParentIteratorVisible = false;
                }
            }

            Model.IsProvenanceMode = false;

            ImageSourceConverter converter = new ImageSourceConverter();
            forActivityHeader.HeaderImage = (ImageSource)converter.ConvertFromString(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/SeqActivity.png");
            forActivityHeader.HeaderBackground = Brushes.AliceBlue;
            forActivityHeader.UpdateClick += delegate(object sender, ModelEventArgs args) { this.UpdateClick.Fire(sender, args); };

            activityStack.Children.Add(forActivityHeader);

            // Set the left and right connector rectangles.
            this.LeftControlFlowConnectionElement = forActivityHeader.LeftControlFlowConnectionElement;
            this.RightControlFlowConnectionElement = forActivityHeader.RightControlFlowConnectionElement;

            // The body of the for activity.
            Grid activityBody = new Grid();

            // Three columns to hold the input param stack, output param stack, and the sequence activity.
            ColumnDefinition gridColumn = new ColumnDefinition();
            gridColumn.Width = new GridLength(0, GridUnitType.Auto);
            activityBody.ColumnDefinitions.Add(gridColumn);

            gridColumn = new ColumnDefinition();
            gridColumn.Width = new GridLength(0, GridUnitType.Auto);
            activityBody.ColumnDefinitions.Add(gridColumn);

            gridColumn = new ColumnDefinition();
            gridColumn.Width = new GridLength(0, GridUnitType.Auto);
            activityBody.ColumnDefinitions.Add(gridColumn);

            this.paramStack = new StackPanel();
            this.paramStack.VerticalAlignment = VerticalAlignment.Stretch;
            Grid.SetColumn(this.paramStack, 0);
            activityBody.Children.Add(this.paramStack);

            Grid stackHolder = new Grid();
            this.paramStack.Children.Add(stackHolder);

            // The input param stack.
            this.inputParamStack = new StackPanel();
            this.inputParamStack.Margin = new Thickness(0, 15, 0, 15);
            this.inputParamStack.VerticalAlignment = VerticalAlignment.Stretch;
            Panel.SetZIndex(this.inputParamStack, 2);
            stackHolder.Children.Add(this.inputParamStack);

            // The border on collapse.
            this.inputParamBorder = new Border();
            this.inputParamBorder.Margin = new Thickness(-12, 5, 0, 5);
            this.inputParamBorder.CornerRadius = new CornerRadius(3);
            this.inputParamBorder.Width = 24.0;
            this.inputParamBorder.BorderThickness = new Thickness(2);
            this.inputParamBorder.HorizontalAlignment = HorizontalAlignment.Left;
            this.inputParamBorder.Background = Brushes.Transparent;
            stackHolder.Children.Add(this.inputParamBorder);

            // The output param stack.
            this.outputParamStack = new StackPanel();
            this.outputParamStack.Margin = new Thickness(0, 15, 0, 15);
            this.outputParamStack.VerticalAlignment = VerticalAlignment.Stretch;
            Grid.SetColumn(this.outputParamStack, 2);
            activityBody.Children.Add(this.outputParamStack);

            // The grid which holds the sequence activity.
            this.activityHolder = new Grid();
            this.activityHolder.Children.Add(this.loopLine);
            this.activityHolder.Children.Add(this.icon);
            this.activityHolder.Margin = new Thickness(0, 25, 0, 0);

            Grid.SetColumn(this.activityHolder, 1);
            activityBody.Children.Add(this.activityHolder);

            activityStack.Children.Add(activityBody);

            this.MainBorder.Child = activityStack;
            this.contentHolder.Children.Add(this.MainBorder);

            this.contentHolder.Margin = new Thickness(5, 5, 45, 5);

            this.loopLine.Stroke = this.borderColor;
            this.loopLine.StrokeThickness = 1.5;

            ScaleTransform scaleTrans = new ScaleTransform(0.7, 0.7);
            this.icon.RenderTransform = scaleTrans;
            this.icon.HorizontalAlignment = HorizontalAlignment.Left;
            this.icon.VerticalAlignment = VerticalAlignment.Top;
            this.Margin = new Thickness(30, 3, 0, 5);
        }

        void headerExtraElements_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            currentModel.SelectedValue = string.Empty;
            currentModel.NextSelectedValue = string.Empty;

            currentModel.SelectedValue = (sender as ComboBox).SelectedValue.ToString();

            if (((sender as ComboBox).Items != null) &&
                (sender as ComboBox).Items.Count > (sender as ComboBox).SelectedIndex + 1)
            {
                currentModel.NextSelectedValue =
                    (sender as ComboBox).Items[(sender as ComboBox).SelectedIndex + 1].ToString();
            }
        }

        /// <summary>
        /// Draw the loop around the sequential activity.
        /// </summary>
        private void DrawConnectors()
        {
            if (this.ChildElements.Count == 1)
            {
                this.loopLine.Points.Clear();

                FrameworkElement leftConnector = this.ChildElements[0].LeftControlFlowConnectionElement;
                FrameworkElement rightConnector = this.ChildElements[0].RightControlFlowConnectionElement;

                Point startLeft = leftConnector.TranslatePoint(new Point(0,
                    leftConnector.ActualHeight / 2.0),
                    this.activityHolder);
                this.loopLine.Points.Add(startLeft);

                Point endRight = rightConnector.TranslatePoint(new Point(rightConnector.ActualWidth,
                    rightConnector.ActualHeight / 2.0),
                    this.activityHolder);

                Point offset1 = new Point(startLeft.X - 15, startLeft.Y);
                this.loopLine.Points.Add(offset1);

                Point offset2 = new Point(offset1.X, offset1.Y - 40);
                this.loopLine.Points.Add(offset2);

                Point offset3 = new Point(endRight.X + 15, offset2.Y);
                this.loopLine.Points.Add(offset3);

                Point offset4 = new Point(offset3.X, endRight.Y);
                this.loopLine.Points.Add(offset4);

                this.loopLine.Points.Add(endRight);

                double left = startLeft.X + (endRight.X - startLeft.X) / 2 - this.icon.ActualWidth / 2;
                double top = startLeft.Y - 53;
                this.icon.Margin = new Thickness(left, top, 0, 0);
            }
        }

        /// <summary>
        /// Everytime a layout change occurs draw the connectors.
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event args.</param>
        void ForActivityUiElement_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.DrawConnectors();
        }

        /// <summary>
        /// Executed when the Ui gets loaded.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ForActivityUiElement_Loaded(object sender, RoutedEventArgs e)
        {
            this.GetComposerControl();
            base.AddInteractionHandlers();
            this.ComposerCtrl.RefreshUI();

            // Validation to restrict the index being bound outside for loop.
            string propertyName = GetValue(TridentUIActivityElement.ValidatePropertyNameProperty) as string;

            // Appened the Property name if there is any previouse propety Name.
            if (!string.IsNullOrEmpty(propertyName))
            {
                propertyName += this.Model.UniqueId;
            }
            else
            {
                propertyName = this.Model.UniqueId;
            }

            SetValue(TridentUIActivityElement.ValidatePropertyNameProperty, propertyName);
            SetValue(TridentUIActivityElement.ValidateBindingProperty, new Collection<Validators.BindingValidator>());
            SetValue(TridentUIActivityElement.ValidationRequiredProperty, true);

            // get the binding validator.
            Validators.BindingValidator bindValid =
                new Validators.BindingValidator(base.ValidatePropertyName, Validators.ValidateBehavior.Contain);

            base.ValidateBinding.Add(bindValid);
        }


        #endregion

        #region public methods

        /// <summary>
        /// Apply seleted style to the workflow activity element.
        /// </summary>
        public override void ApplySelectedStyle()
        {
            // Apply the background of the border.
            this.MainBorder.BorderBrush = Brushes.Orange;
        }

        /// <summary>
        /// Apply Unseleted stlye to the workflow activity element.
        /// </summary>
        public override void ApplyUnselectedStyle()
        {
            // Revert back all the changes made while selecting.
            this.MainBorder.BorderBrush = this.borderColor;
        }

        /// <summary>
        /// Display the parameter outline.
        /// </summary>
        public override void ShowParameterOutline(bool showInputOutline)
        {
            if (showInputOutline)
            {
                this.inputParamBorder.BorderBrush = this.borderColor;
            }
        }

        /// <summary>
        /// Hide the parameter outline.
        /// </summary>
        public override void HideParameterOutline(bool hideInputOutline)
        {
            if (hideInputOutline)
            {
                this.inputParamBorder.BorderBrush = Brushes.Transparent;
            }
        }

        #endregion

        #region IBindingEndpoint Members

        public override Point GetBindingStartPoint(UIElement relativeTo)
        {
            if (this.Model.IsCollapsed)
            {
                // Get the start point in the activity header.
                return new Point();
            }
            else
            {
                return this.outputParamElement.GetBindingStartPoint(relativeTo);
            }
        }

        public override Point GetBindingEndpoint(UIElement relativeTo)
        {
            if (this.Model.IsCollapsed)
            {
                // Get the start point in the activity header.
                return new Point();
            }
            else
            {
                // Get the midpoint of the outline.
                Point startPoint = new Point(0, this.inputParamBorder.ActualHeight / 2.0);
                return this.inputParamBorder.TranslatePoint(startPoint, relativeTo);
            }
        }

        #endregion

        #region ICompositeUiElement Members

        public ObservableCollection<ITridentUIElement> ChildElements
        {
            get
            {
                return this.childElements;
            }
        }

        public void AddChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            this.childElements.Add(childElement);
            UIElement uiElem = childElement as UIElement;
            if (uiElem != null)
            {
                this.activityHolder.Children.Add(uiElem);
            }
        }

        public void InsertChildElement(ITridentUIElement childElement, int indexToInsertAt)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            if (0 > indexToInsertAt)
            {
                throw new ArgumentOutOfRangeException("indexToInsertAt");
            }

            #endregion Parameters check.

            this.ChildElements.Insert(indexToInsertAt, childElement);
            UIElement uiElem = childElement as UIElement;
            if (uiElem != null)
            {
                this.activityHolder.Children.Insert(indexToInsertAt, uiElem);
            }
        }

        public bool RemoveChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            bool opStatus = false;
            if (this.ChildElements.Contains(childElement))
            {
                opStatus = this.ChildElements.Remove(childElement);
            }

            if (opStatus)
            {
                UIElement uiElem = childElement as UIElement;
                if (uiElem != null && this.activityHolder.Children.Contains(uiElem))
                {
                    this.activityHolder.Children.Remove(uiElem);
                }
            }

            return opStatus;
        }

        public void RemoveChildElementAt(int indexOfChild)
        {
            #region Parameters check.

            if (0 > indexOfChild || (this.ChildElements.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            if (0 > indexOfChild || (this.activityHolder.Children.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            #endregion Parameters check.

            this.ChildElements.RemoveAt(indexOfChild);
            this.activityHolder.Children.RemoveAt(indexOfChild);
        }

        public Panel MainContentHolder
        {
            get { return this.activityHolder; }
        }
        
        public override event EventHandler<ModelEventArgs> UpdateClick;

        #endregion
    }
}
